﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web;
using AWS.SLAM.Configuration;
using AWS.SLAM.Hierarchy.Data;
using AWS.SLAM.SharePoint;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;

namespace AWS.SLAM.Hierarchy
{
	public partial class HierarchyPositionFieldControl : BaseFieldControl
	{
		protected DropDownList ddlParents;
		protected DropDownList ddlPositions;
		protected HtmlGenericControl editableArea;
		protected HtmlGenericControl positionArea;
		protected HiddenField hdnSaveOperation;
		protected Literal ltParentLinks;
		protected Label lblMessages;

		private string _dataSchema;
		private string _tableName;
		private string _connectionString;
		private string _columnName;
		private string _idColumnName;

		public string ItemDisplayName
		{
			get;
			set;
		}

		protected override string DefaultTemplateName
		{
			get
			{
				return "SLAMHierarchyPositionFieldControl";
			}
		}

		public override string DisplayTemplateName
		{
			get
			{
				return "SLAMHierarchyPositionFieldDisplayControl";
			}
			set
			{
				base.DisplayTemplateName = value;
			}
		}

		public override object Value
		{
			get
			{
				this.EnsureChildControls();
				string currentValue = String.Format("{0}|{1}", ddlParents.SelectedValue, ddlPositions.SelectedValue);
				if (hdnSaveOperation.Value != "a")
				{
					if (ItemFieldValue != null && ddlParents.SelectedValue == Guid.Empty.ToString() && ItemFieldValue.ToString().Substring(0, ItemFieldValue.ToString().LastIndexOf("|")) == currentValue)
					{
						hdnSaveOperation.Value = "s";
					}
					else
					{
						hdnSaveOperation.Value = "m";
					}
				}
				return currentValue + "|" + hdnSaveOperation.Value;
			}
			set
			{
				this.EnsureChildControls();
				if (value != null && ddlParents != null)
				{
					string[] valueParts = value.ToString().Split('|');

					if (!String.IsNullOrEmpty(valueParts[0]) && valueParts[0] != Guid.Empty.ToString())
					{
						LoadChildrenForParent(valueParts[0]);

						ListItem selectedParent = ddlParents.Items.FindByValue(valueParts[0]);
						if (selectedParent != null)
						{
							selectedParent.Selected = true;

							ListItem selectedPostiion = ddlPositions.Items.FindByValue(Convert.ToString(ListItem.GetUniqueId()));
							if (selectedPostiion != null)
							{
								selectedPostiion.Selected = true;
							}
						}
						else
						{
							hdnSaveOperation.Value = "a";
							positionArea.Visible = false;
						}
					}
					else
					{
						positionArea.Visible = false;
					}
				}
			}
		}


		protected override void CreateChildControls()
		{
			if (Field == null)
				return;

			base.CreateChildControls();

			if (ControlMode == SPControlMode.Display)
			{
				lblMessages = (Label)TemplateContainer.FindControl("lblMessages");
				ltParentLinks = (Literal)TemplateContainer.FindControl("ltParentLinks");
				if (InstallationValidator.VerifySlamEventsAreAttached(List))
				{
					if (this.ItemFieldValue != null)
					{
						try
						{
							LoadDatabaseInformation();

							if (VerifyTargetContainsHierarchy())
							{
								DataContext dataContext = new DataContext(_connectionString);
								foreach (var hierarchyItem in dataContext.ExecuteQuery<HierarchyDisplayItem>(String.Format("select t2.ListRelativeID, t2.{0} Name from {1}{2} t1, {1}{2} t2 where t1.ListID = '{4}' and t1.ListRelativeID = '{3}' and t2.ListID = '{4}' and t2.Lft <= t1.Lft and t2.Rght >= t1.Rght order by t2.Lft ", GetTableName(_columnName), _dataSchema, GetTableName(_tableName), ItemId.ToString(), List.ID.ToString())))
								{
									if (hierarchyItem.ListRelativeID == ItemId && String.IsNullOrEmpty(ltParentLinks.Text))
									{
										ltParentLinks.Text = "[Root]";
									}
									else if (hierarchyItem.ListRelativeID == ItemId && !String.IsNullOrEmpty(ltParentLinks.Text))
									{
										ltParentLinks.Text = ltParentLinks.Text.Substring(0, ltParentLinks.Text.Length - 3);
									}
									else
									{
										ltParentLinks.Text += String.Format("<a href=\"{0}\">{1}</a> > ", List.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl + "?ID=" + hierarchyItem.ListRelativeID.ToString(), hierarchyItem.Name);
									}
								}

								if (String.IsNullOrEmpty(ltParentLinks.Text))
								{
									ltParentLinks.Text = "[Root]";
								}
							}
						}
						catch
						{
							lblMessages.Text = "This item's Hierarchy position could not be found, please ensure that this item's List is configured to be managed by the SharePoint List Association Manager (SLAM) and that SLAM is active.";
							lblMessages.Visible = true;
							ltParentLinks.Visible = false;
						}
					}
				}
				else
				{
					if (List.DoesUserHavePermissions(SPBasePermissions.ManageWeb))
					{
						lblMessages.Text = "In order for this field to function properly SharePoint List Association Manager (SLAM) must be configured to manage this List Item, and it must be activated against this List or Content Type.<br /><br />SLAM Events were not found.";
					}
					else
					{
						lblMessages.Text = "This field is not configured properly, cannot display value.";
					}
					lblMessages.Visible = true;
					ltParentLinks.Visible = false;
				}
			}
			else
			{

				if (!base.IsFieldValueCached)
				{
					lblMessages = (Label)TemplateContainer.FindControl("lblMessages");
					positionArea = (HtmlGenericControl)TemplateContainer.FindControl("positionArea");
					editableArea = (HtmlGenericControl)TemplateContainer.FindControl("editableArea");
					ddlParents = (DropDownList)TemplateContainer.FindControl("ddlParents");
					ddlPositions = (DropDownList)TemplateContainer.FindControl("ddlPositions");
					hdnSaveOperation = (HiddenField)TemplateContainer.FindControl("hdnSaveOperation");

					ddlParents.AutoPostBack = true;
					ddlParents.SelectedIndexChanged += new EventHandler(ddlParents_SelectedIndexChanged);

					if (InstallationValidator.VerifySlamEventsAreAttached(List))
					{
						try
						{
							LoadDatabaseInformation();
							LoadParentOptions();

							if (this.ItemFieldValue != null)
							{
								this.Value = ItemFieldValue;
							}
							else
							{
								LoadChildrenForParent(Guid.Empty.ToString());
								positionArea.Visible = false;
							}
						}
						catch (Exception ex)
						{
							lblMessages.Text = "This item's Hierarchy position could not be found, please ensure that this item's List is configured to be managed by the SharePoint List Association Manager (SLAM) and that SLAM is active.";
							lblMessages.Visible = true;
							editableArea.Visible = false;
						}
					}
					else
					{
						if (List.DoesUserHavePermissions(SPBasePermissions.ManageWeb))
						{
							lblMessages.Text = "In order for this field to function properly SharePoint List Association Manager (SLAM) must be configured to manage this List Item, and it must be activated against this List or Content Type.<br /><br />SLAM Events were not found.";
						}
						else
						{
							lblMessages.Text = "This field is not configured properly, cannot edit value.";
						}
						lblMessages.Visible = true;
						editableArea.Visible = false;
					}
				}
			}
		}

		private void LoadDatabaseInformation()
		{
			IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
			string siteCollection = SPContext.GetContext(HttpContext.Current).Site.ServerRelativeUrl.TrimStart('/');
			DataMapping currentDataMapping = slamConfiguration.DataMapping;
			if (!String.IsNullOrEmpty(siteCollection) && slamConfiguration.DataMappings.ContainsKey(siteCollection))
			{
				currentDataMapping = slamConfiguration.DataMappings[siteCollection];
			}
			if (slamConfiguration is XmlConfigurationManager)
			{
				_dataSchema = ((XmlSqlDataMapping)currentDataMapping).GetDataSchema() + ".";
			}

			ConfiguredType type = FindSlamConfiguredType(currentDataMapping);
			_tableName = type.Name;
			if (type is SqlConfiguredType)
			{
				SqlConfiguredType sqlType = (SqlConfiguredType)type;
				if (!String.IsNullOrEmpty(sqlType.Table))
				{
					_tableName = sqlType.Table;
				}

				foreach (Field field in currentDataMapping.GetTypeFields(type.Id, type.Name))
				{
					if (field.Name == ItemDisplayName && field is SqlMappedField)
					{
						SqlMappedField sqlField = (SqlMappedField)field;
						_columnName = String.IsNullOrEmpty(sqlField.Column) ? field.Name : sqlField.Column;
						break;
					}
				}
			}

			_connectionString = "";
			if (currentDataMapping is XmlSqlDataMapping)
			{
				_connectionString = ((XmlSqlDataMapping)currentDataMapping).GetConnectionString();
			}

			if (String.IsNullOrEmpty(_connectionString))
			{
				_connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
			}
			else
			{
				_connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(_connectionString);
			}

			TypeIdDefinition typeIdDefinition = new TypeIdDefinition();
			string typeIdDefinitionName = currentDataMapping.GetTypeAttribute(type.Id, type.Name, "TypeIdDefinition");
			if (String.IsNullOrEmpty(typeIdDefinitionName))
			{
				typeIdDefinitionName = ((XmlSqlDataMapping)currentDataMapping).GetTypeIdDefinitionName();
			}
			TypeIdDefinitionSection typeIdDefininitionSection = slamConfiguration.GetSection<TypeIdDefinitionSection>("TypeIdDefinitions");

			if (String.IsNullOrEmpty(typeIdDefinitionName))
			{
				typeIdDefinition = typeIdDefininitionSection.Default;
			}
			else
			{
				typeIdDefinition = typeIdDefininitionSection.GetTypeIdDefinition(typeIdDefinitionName);
			}

			_idColumnName = GetTableName(String.IsNullOrEmpty(typeIdDefinition.NameFormat) ? "ID" : typeIdDefinition.NameFormat.Replace("{TypeName}", currentDataMapping.GetTypeAttribute(type.Id, type.Name, "Name")).Replace("{TableName}", _tableName));
		}

		private void LoadParentOptions()
		{
			ddlParents.Items.Clear();

			if (VerifyTargetContainsHierarchy())
			{
				DataContext dataContext = new DataContext(_connectionString);

				Stack<string> parentage = new Stack<string>();
				Stack<int> parentRights = new Stack<int>();

				if (ControlMode == SPControlMode.Edit)
				{
					foreach (var hierarchyItem in dataContext.ExecuteQuery<HierarchyParentItem>(String.Format("select {5} ListItemID, {0} Name, Lft [Left], Rght [Right] from {1}{2} t1, SLAM.List l where t1.ListID = l.ListID and l.ID = '{4}' and Lft is not null and not exists (select * from {1}{2} t2, SLAM.List l where l.ListID = t2.ListID  and l.ID = '{4}' and t2.{5} = '{3}' and t1.Lft > t2.Lft and t1.Rght < t2.Rght) order by Lft ", GetTableName(_columnName), _dataSchema, GetTableName(_tableName), Convert.ToString(ListItem.GetUniqueId()), List.ID.ToString(), _idColumnName)))
					{
						if (hierarchyItem.ListItemID != Convert.ToString(ListItem.GetUniqueId()))
						{
							while (parentRights.Count > 0 && hierarchyItem.Left >= parentRights.Peek())
							{
								parentage.Pop();
								parentRights.Pop();
							}

							parentage.Push(hierarchyItem.Name);
							parentRights.Push(hierarchyItem.Right);

							ddlParents.Items.Add(new ListItem(String.Join(" > ", parentage.Reverse().ToArray()), hierarchyItem.ListItemID));
						}
					}
				}
				else if (ControlMode == SPControlMode.New)
				{
					foreach (var hierarchyItem in dataContext.ExecuteQuery<HierarchyParentItem>(String.Format("select {4} ListItemID, {0} Name, Lft [Left], Rght [Right] from {1}{2} t1, SLAM.List l where l.ListID = t1.ListID and l.ID = '{3}' and Lft is not null order by Lft ", GetTableName(_columnName), _dataSchema, GetTableName(_tableName), List.ID.ToString(), _idColumnName)))
					{
						while (parentRights.Count > 0 && hierarchyItem.Left >= parentRights.Peek())
						{
							parentage.Pop();
							parentRights.Pop();
						}

						parentage.Push(hierarchyItem.Name);
						parentRights.Push(hierarchyItem.Right);

						ddlParents.Items.Add(new ListItem(String.Join(" > ", parentage.Reverse().ToArray()), hierarchyItem.ListItemID));
					}

					hdnSaveOperation.Value = "a";
				}
			}
			ddlParents.Items.Insert(0, new ListItem("[Root]", Guid.Empty.ToString()));
			ddlParents.DataBind();
		}

		private void LoadChildrenForParent(string parentId)
		{
			ddlPositions.Items.Clear();

			if (VerifyTargetContainsHierarchy())
			{
				DataContext dataContext = new DataContext(_connectionString);
				foreach (var hierarchyItem in dataContext.ExecuteQuery<HierarchyChildItem>(
					String.Format(@"select t2.[{5}] ListItemID, t2.{0} Name from {1}{2} t1, {1}{2} t2, SLAM.List l1, SLAM.List l2
									where t1.[{5}] = '{3}' and l1.ListID = t1.ListID and l1.ID = '{4}' and l2.ListID = t2.ListID and l2.ID = '{4}' and t2.Lft > t1.Lft and t2.Rght < t1.Rght 
									group by t1.Lft, t2.Lft, t1.Rght, t2.Rght, t2.[{5}], t2.{0}
									having (select COUNT(*) children from {1}{2} inh, SLAM.List inl where inl.ListID = inh.ListID and inl.ID = '{4}' and inh.Lft >= t1.Lft and inh.Rght <= t1.Rght and Lft < t2.Lft and Rght > t2.Rght) = 1
									order by t2.Lft ", GetTableName(_columnName), _dataSchema, GetTableName(_tableName), parentId, List.ID.ToString(), _idColumnName)))
				{
					ddlPositions.Items.Add(new ListItem(hierarchyItem.Name, hierarchyItem.ListItemID));
				}
			}

			ddlPositions.Items.Add(new ListItem("[Last]", Guid.Empty.ToString()));
			ddlPositions.DataBind();
		}

		private ConfiguredType FindSlamConfiguredType(DataMapping dataMapping)
		{
			ConfiguredType configuredType = null;
			bool configuredContentType = false;
			foreach (SPEventReceiverDefinition receiver in List.EventReceivers)
			{
				if (receiver.Name.StartsWith("SLAM") && receiver.Class.Contains("ContentTypeEventManager"))
				{
					configuredContentType = true;
					break;
				}
			}

			SPContentType contentType = null;
			if (configuredContentType && List.AllowContentTypes)
			{
				contentType = ListItem.ContentType;
				if (contentType == null && Context.Request.QueryString["ContentTypeId"] != null)
				{
					contentType = List.ContentTypes[new SPContentTypeId(Context.Request.QueryString["ContentTypeId"])];
				}

				if (contentType == null)
				{
					contentType = List.ContentTypes[0];
				}
			}
			foreach (ConfiguredType type in dataMapping.GetAllTypes())
			{
				if (configuredContentType && type.ContentType && contentType != null && contentType.EqualsOrInherits(type.Name))
				{
					configuredType = type;
					break;
				}
				else if (!configuredContentType && (type.Id == ListItem.ParentList.ID.ToString() || type.Name == ListItem.ParentList.Title))
				{
					configuredType = type;
					break;
				}
			}

			return configuredType;
		}

		private bool VerifyTargetContainsHierarchy()
		{
			DataService dataService = new DataService(_connectionString, _dataSchema.TrimEnd('.'));
			return dataService.VerifyHierarchyColumnsExist(_tableName);
		}

		protected void ddlParents_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (ddlParents.SelectedValue != Guid.Empty.ToString())
			{
				positionArea.Visible = true;
				LoadChildrenForParent(ddlParents.SelectedValue);
				ddlPositions.SelectedIndex = ddlPositions.Items.Count - 1;
			}
			else
			{
				positionArea.Visible = false;
			}
		}

		private string GetTableName(string typeName)
		{
			return Regex.Replace(typeName, "[\\W\\s]", "");
		}
	}

	public class HierarchyDisplayItem
	{
		public int ListRelativeID
		{
			get;
			set;
		}

		public string Name
		{
			get;
			set;
		}

		public int Left
		{
			get;
			set;
		}

		public int Right
		{
			get;
			set;
		}
	}

	public class HierarchyChildItem
	{
		public string ListItemID
		{
			get;
			set;
		}

		public string Name
		{
			get;
			set;
		}
	}

	public class HierarchyParentItem
	{
		public string ListItemID
		{
			get;
			set;
		}

		public string Name
		{
			get;
			set;
		}

		public int Left
		{
			get;
			set;
		}

		public int Right
		{
			get;
			set;
		}
	}
}